﻿using System;
using System.Collections.Specialized;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Web.Http;

using Newtonsoft.Json;

using Sustainalytics.OAuth.Services.Common;
using Sustainalytics.Portfolios.BL;
using Sustainalytics.Portfolios.BL.Exceptions;
using Sustainalytics.Portfolios.Service.Models;

using WebApi.OutputCache.V2;
using System.Web.Http.Cors;

namespace Sustainalytics.Portfolios.Service.Controllers
{
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [RoutePrefix("api/v1/uploadPortfolioSecurity")]
    public class PortfolioSecuritiesUploadController : ApiController
    {
        private readonly IPortfolioSecuritiesImportManagement _portfolioSecurityManager;

        private readonly PortfolioUserClaimsProcessor _userClaimsProcessor;

        public PortfolioSecuritiesUploadController(
            IPortfolioSecuritiesImportManagement portfolioSecurityManager, PortfolioUserClaimsProcessor userClaimsProcessor)
        {
            this._portfolioSecurityManager = portfolioSecurityManager;
            this._userClaimsProcessor = userClaimsProcessor;
        }

        [Route("")]
        public HttpResponseMessage Post()
        {
            if (!this.Request.Content.IsMimeMultipartContent())
            {
                return this.Request.CreateResponse(HttpStatusCode.MethodNotAllowed);
            }

            try
            {
                var temporaryFolder = GetTemporaryFolder();

                var multipartProvider = new MultipartFormDataStreamProvider(temporaryFolder);

                this.Request.Content.ReadAsMultipartAsync(multipartProvider).Wait();

                var userClaims = _userClaimsProcessor.GetPortfolioClaims();

                var uploadParamDto = GetUploadParamsFromFormData(multipartProvider.FormData);

                var portfolioUploadParam = uploadParamDto.ToDto();

                var multipartFileName = ProcessMultipartFormData(multipartProvider);


                _portfolioSecurityManager.SavePortfolioFile(multipartFileName, portfolioUploadParam);

                var uploadResult = this._portfolioSecurityManager.SavePortfolioMatches(
                    multipartFileName,
                    portfolioUploadParam,
                    userClaims);

                // invalidate cache for parent resource 
                var cache = this.Configuration.CacheOutputConfiguration().GetCacheOutputProvider(this.Request);
                cache.RemoveStartsWith(this.Configuration.CacheOutputConfiguration().MakeBaseCachekey("Portfolios", "Get"));

                return this.BuildHttpResponseMessage(
                    uploadResult.FileContent,
                    GetUploadFileName(portfolioUploadParam.PortfolioName),
                    string.Empty,
                    uploadResult.MatchingSummary,
                    portfolioUploadParam.PortfolioName);
            }
            catch (InvalidPortfolioTemplate ex)
            {
                return this.BuildHttpResponseMessage(
                    new byte[0],
                    ex.FileName,
                    "Uploaded file does not match template file",
                    string.Empty,
                    string.Empty);
            }
        }

        private static MultipartFormDataContent CreateMultipartFormDataResponse(
            byte[] processedFileContent,
            string fileName,
            string errors,
            string details,
            string portfolioName)
        {
            var multipartContent = new MultipartFormDataContent();

            // add details to multipart content
            var detailsContent = new ObjectContent<string>(details, new JsonMediaTypeFormatter());
            multipartContent.Add(detailsContent, "detailsParam");

            // add errors to multipart content
            var errorsContent = new ObjectContent<string>(errors, new JsonMediaTypeFormatter());
            multipartContent.Add(errorsContent, "errorsParam");

            var portfolioNameContent = new ObjectContent<string>(portfolioName, new JsonMediaTypeFormatter());
            multipartContent.Add(portfolioNameContent, "portfolioNameParam");

            var fileContent = new ByteArrayContent(processedFileContent);
            fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
            {
                FileName = fileName
            };

            fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

            // add the bytes to the multipart message
            multipartContent.Add(fileContent);

            return multipartContent;
        }

        private static string GetTemporaryFolder()
        {
            var temporaryFolder = ConfigurationManager.AppSettings["portfolioUploadTempFolder"];

            if (string.IsNullOrEmpty(temporaryFolder))
            {
                throw new NullReferenceException(
                    "Portfolio temporary upload folder not defined in appSettings :: portfolioUploadTempFolder.");
            }

            try
            {
                if (Directory.Exists(temporaryFolder) == false)
                {
                    Directory.CreateDirectory(temporaryFolder);
                }
            }
            catch (Exception ex)
            {
                throw new Exception(
                    string.Format(
                        "Portfolio upload temporary folder does not exist and couldn't be created. Path: {0}",
                        temporaryFolder),
                    ex);
            }

            return temporaryFolder;
        }

        private static string GetUploadFileName(string portfolioName)
        {
            var fileName = portfolioName + "_Company_Match.xlsx";
            return fileName;
        }

        private static PortfolioUploadDto GetUploadParamsFromFormData(NameValueCollection formData)
        {
            string uploadParamStr = formData["uploadParam"];

            if (string.IsNullOrEmpty(uploadParamStr))
            {
                throw new Exception("Portfolio uploadParam is missing from request.");
            }

            var uploadParam = JsonConvert.DeserializeObject<PortfolioUploadDto>(uploadParamStr);

            return uploadParam;
        }

        private static string ProcessMultipartFormData(MultipartFileStreamProvider provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            var portfolioMultipartFile = provider.FileData.FirstOrDefault();

            if (portfolioMultipartFile == null)
            {
                throw new Exception("No multipart portfolio file data.");
            }

            var processedFileContent = new PushStreamContent(
                (stream, content, context) => { },
                portfolioMultipartFile.Headers.ContentType.MediaType);

            processedFileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachement")
            {
                FileName = portfolioMultipartFile.Headers.ContentDisposition.FileName
            };
            processedFileContent.Headers.ContentType =
                new MediaTypeHeaderValue(portfolioMultipartFile.Headers.ContentType.MediaType);

            return portfolioMultipartFile.LocalFileName;
        }

        private HttpResponseMessage BuildHttpResponseMessage(
            byte[] content,
            string fileName,
            string error,
            string infoMessage,
            string portfolioName)

        {
            var response = this.Request.CreateResponse(HttpStatusCode.OK);

            var multipartContent = CreateMultipartFormDataResponse(content, fileName, error, infoMessage, portfolioName);
            response.Content = multipartContent;
            return response;
        }
    }
}